package com.gitee.wsl.ext.number

import kotlin.math.ceil
import kotlin.math.floor
import kotlin.math.pow
import kotlin.math.round
import kotlin.math.roundToInt
import kotlin.math.roundToLong

/** Converts [this] into [Int] rounding to the nearest */
fun Float.toIntRound(): Int = round(this).toInt()
/** Converts [this] into [Int] rounding to the nearest */
fun Double.toIntRound(): Int = round(this).toInt()

/** Converts [this] into [Int] rounding to the nearest */
fun Float.toLongRound(): Long = round(this).toLong()
/** Converts [this] into [Int] rounding to the nearest */
fun Double.toLongRound(): Long = round(this).toLong()

/**
 * Round double value to 2 decimal point
 */
fun Float.roundTwoDecimal(): Float {
    return (this * 100.0f).roundToInt() / 100.0f
}

fun Float.roundDecimalPlaces(places: Int): Float {
    if (places < 0) return this
    val placesFactor: Float = 10f.pow(places.toFloat())
    return round(this * placesFactor) / placesFactor
}

/**
 * Round double value to 2 decimal point
 */
fun Double.roundTwoDecimal(): Double {
    return (this * 100.0).roundToInt() / 100.0
}

fun Double.roundDecimalPlaces(places: Int): Double {
    if (places < 0) return this
    val placesFactor: Double = 10.0.pow(places.toDouble())
    return round(this * placesFactor) / placesFactor
}

/**
 * Electricity consumption data is returned to the nearest 0.001kwh.
 * For billing, consumption is rounded to the nearest 0.01kwh before multiplying by the price.
 * The rounding method used is rounding half to even, where numbers ending in 5 are rounded up or down, towards the nearest even hundredth decimal place.
 * As a result, 0.015 would be rounded up to 0.02, while 0.025 is rounded down to 0.02.
 */
fun Double.roundConsumptionToNearestEvenHundredth(): Double {
    val scaled = this * 100
    val rounded = if (scaled % 1.0 == 0.5) {
        if (scaled.toLong() % 2 == 0L) {
            scaled.toLong() // It's already even
        } else {
            scaled.toLong() + 1 // Round up to the nearest even
        }
    } else {
        scaled.roundToLong()
    }
    return rounded / 100.0
}


/**
 * # Rounding Mode
 *
 * Specifies different strategies for rounding floating-point numbers.
 * Used to determine the behavior when a number is exactly halfway between two possible rounded values.
 *
 * Used in the [round] function.
 */
public enum class RoundingMode {
    /**
     * Always rounds towards positive infinity.
     * Example: 1.5 becomes 2.0, -1.5 becomes -1.0.
     */
    UP,

    /**
     * Always rounds towards negative infinity.
     * Example: 1.5 becomes 1.0, -1.5 becomes -2.0.
     */
    DOWN,

    /**
     * Rounds towards the nearest neighbor unless both neighbors are equidistant,
     * in which case it rounds up. Commonly known as "round half up."
     * Example: 1.5 becomes 2.0, 1.49 becomes 1.0.
     */
    HALF_UP,

    /**
     * Rounds towards the nearest neighbor unless both neighbors are equidistant,
     * in which case it rounds down. Known as "round half down."
     * Example: 1.5 becomes 1.0, 1.51 becomes 2.0.
     */
    HALF_DOWN,

    /**
     * Rounds towards the nearest neighbor unless both neighbors are equidistant,
     * in which case it rounds towards the nearest even number. Known as "bankers' rounding."
     * Example: 1.5 becomes 2.0, 2.5 becomes 2.0.
     */
    HALF_EVEN
}

private fun roundImpl(
    factor: Double,
    scaledNumber: Double,
    strategy: RoundingMode
): Double {
    return when (strategy) {
        RoundingMode.UP -> ceil(scaledNumber) / factor
        RoundingMode.DOWN -> floor(scaledNumber) / factor
        RoundingMode.HALF_UP -> {
            if (scaledNumber % 1 >= 0.5) ceil(scaledNumber) / factor
            else floor(scaledNumber) / factor
        }

        RoundingMode.HALF_DOWN -> {
            if (scaledNumber % 1 > 0.5) ceil(scaledNumber) / factor
            else floor(scaledNumber) / factor
        }

        RoundingMode.HALF_EVEN -> {
            if (scaledNumber % 1 == 0.5) {
                if ((scaledNumber.toLong() % 2) == 0L) floor(scaledNumber) / factor
                else ceil(scaledNumber) / factor
            } else {
                round(scaledNumber) / factor
            }
        }
    }
}

fun Double.round(
    decimalPlaces: Int,
    strategy: RoundingMode = RoundingMode.HALF_UP
): Double {
    require(decimalPlaces >= 0) { "Decimal places must be positive." }

    val factor = 10.0.pow(decimalPlaces)
    val scaledNumber = this * factor

    return roundImpl(factor, scaledNumber, strategy)
}


fun Float.round(
    decimalPlaces: Int,
    strategy: RoundingMode = RoundingMode.HALF_UP
): Float {
    require(decimalPlaces >= 0) { "Decimal places must be positive." }

    val factor = 10.0.pow(decimalPlaces)
    val scaledNumber = this * factor

    return roundImpl(factor, scaledNumber, strategy).toFloat()
}